package org.lisy.java.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 序列化操作
 * 
 * @author lisy
 */
public class SerializableOperate {

	// 目标文件目录
	private final static String TARGET_SERIALIZABLE_FILE_DIR = "./deploy/target/serializable";
	// 目标序列化文件
	private final static String TARGET_SERIALIZABLE_FILE = "target.txt";
	
	/**
	 * 序列化: 序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或 XML 格式传输
	 * 反序列化: 把字节序列恢复为对象的过程称为对象的反序列化
	 * static: 不序列化，序列化保存的是对象的状态，静态变量属于类的状态
	 * transient 关键字的作用是控制变量的序列化，在变量声明前加上该关键字，可以阻止该变量被序列化到文件中，在被反序列化后，transient 变量的值被设为初始值，如 int 型的是 0，对象型的是 null
	 * 在进行序列化操作时，会判断要被序列化的类是否是 Enum、Array 和 Serializable 类型，如果都不是则直接抛出 NotSerializableException
	 * Serializable 接口: 
	 *   当一个父类实现序列化，子类自动实现序列化，不需要显式实现 Serializable 接口
	 *   一个子类实现了 Serializable 接口，它的父类都没有实现 Serializable 接口，要想将父类对象也序列化，就需要让父类也实现Serializable 接口;如果
	 * 父类不实现 Serializable 接口的话，就需要有默认的无参的构造函数。这是因为一个 Java 对象的构造必须先有父对象，才有子对象，反序列化也不例外。在反序列化时，
	 * 为了构造父对象，只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时，它的值是调用父类无参构造函数后的值。在这种情况下，在序列化时根据需要在
	 * 父类无参构造函数中对变量进行初始化，否则的话，父类变量值都是默认声明的值
	 *   当试图对一个对象进行序列化的时候，如果遇到不支持 Serializable 接口的对象。在此情况下，将抛出 NotSerializableException
	 * Externalizable 接口: 
	 *   Externalizable 继承了 Serializable，该接口中定义了两个抽象方法：writeExternal ()与 readExternal()。当使用 Externalizable 接口来进行
	 * 序列化与反序列化的时候需要开发人员重写 writeExternal()与 readExternal()方法
	 *   在使用 Externali zable 进行序列化的时候，在读取对象时，会调用被序列化类的无参构造器去创建一个新的对象，然后再将被保存对象的字段的值
	 * 分别填充到新对象中。所以，实现 Externalizable 接口的类必须要提供一个 public 的无参的构造器
	 *   没有无参数的构造函数，在运行时会抛出异常：java.io.InvalidClass Exception
	 * 
	 * 序列化可能破坏单例:readObject - readObject0 - readOrdinaryObject- checkResolve
	 *   在 Singleton 类中定义 readResolve 就可以防止序列化破坏单例模式
	 * fastjson 的反序列化漏洞:
	 *   当一个类中包含了一个接口（或抽象类）的时候，在使用fastjson 进行序列化的时候，会将子类型抹去，只保留接口（抽象类）的类型，使得反序列化时无法拿到原始类型
	 *   fastjson 引入了 AutoType，即在序列化的时候，把原始类型记录下来
	 *   设置了 safeMode 后，@type 字段不再生效，即当解析形如{"@type": "com.java.class"}的 JSON 串时，将不再反序列化出对应的类
	 *     ParserConfig.getGlobalInstance().setSafeMode(true);
	 */
	public static void main(String[] args) {
		// 序列化
		serializeMethod();
		// 反序列化
		deserializeMethod();
	}

	private static void serializeMethod() {
		Person person = new Person(1, "test");
        person.setName("li");
        person.setAge(20);
        person.setHobby("music");
        // ObjectOutputStream 对象输出流，将 person 对象存储到文件中，完成对 person 对象的序列化操作
        ObjectOutputStream oos = null;
        try {
        	String filePath = TARGET_SERIALIZABLE_FILE_DIR + File.separator + TARGET_SERIALIZABLE_FILE;
            File file = new File(filePath);
     		if (!file.exists()) {
     			// 如果上级不存在，就会自动创建
     			file.getParentFile().mkdirs();
     		} else {
     			file.delete();
     		}
			oos = new ObjectOutputStream(new FileOutputStream(file));
			oos.writeObject(person);
			System.out.println("serialize finish " + person);
	        oos.close();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (oos != null) {
				try {
					oos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private static void deserializeMethod() {
		ObjectInputStream ois = null;
		try {
			String filePath = TARGET_SERIALIZABLE_FILE_DIR + File.separator + TARGET_SERIALIZABLE_FILE;
			ois = new ObjectInputStream(new FileInputStream(new File(filePath)));
			Person person = (Person) ois.readObject();
			System.out.println("deserialize finish " + person);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			if (ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

class Person extends People implements Serializable {
	
	/**
	 * 如果 Person 类的 serialVersionUID 不一致，那么反序列化就不能正常进行
	 * 通过改变序列化 ID 可以用来限制某些用户的使用
	 */
	private static final long serialVersionUID = 1L;
	
	private String name;
	private int age;
	private static String MARK = "mark";
	transient String hobby;
	
	// 单独执行方法测试
	public Person(int num, String mark) {
		super(num);
		Person.MARK = mark;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getHobby() {
		return hobby;
	}
	public void setHobby(String hobby) {
		this.hobby = hobby;
	}
	
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", mark=" + MARK + ", hobby=" + hobby + ", num=" + getNum() + "]";
	}
}

class People implements Serializable {
    
	private static final long serialVersionUID = 1L;
	
	int num;
    
    public People(){}
    
    public People(int num){
        this.num = num;
    }
    
    public int getNum() {
		return num;
	}

	public String toString(){
        return "num:" + num;
    }
}
